<#include "license.ftl">
<@license/>
<#assign object = doc.object>
package ${object.@package}.service;

<#macro copy_resultset_to_pojo att>
    <#if att?node_name != "object" && att?node_type == "element">
        <#assign position = position + 1>
                position = ${position};
        <#if att?node_name == "boolean">
                boolean _${att.@fieldName} = rs.getBoolean(${position} + offset);
                if (!rs.wasNull()) {
                    retval.${att.@fieldName} = _${att.@fieldName};
                } else {
                    retval.${att.@fieldName} = null;
                }
        <#elseif att?node_name == "date">
                if (rs.getDate(${position} + offset) != null) {
                    retval.${att.@fieldName} = new Date(rs.getDate(${position} + offset).getTime());
                } else {
                    retval.${att.@fieldName} = null;
                }
        <#elseif att?node_name == "datetime">
                if (rs.getTimestamp(${position} + offset) != null) {
                    retval.${att.@fieldName} = new Date(rs.getTimestamp(${position} + offset).getTime());
                } else {
                    retval.${att.@fieldName} = null;
                }
        <#elseif att?node_name=="string" || att?node_name == "html">
            <#if att?node_name=="html" || att.@maxlength[0]?number &gt; 65000>
                InputStream ${att.@fieldName}Stream = rs.getBinaryStream(${position} + offset);
                if (${att.@fieldName}Stream != null) {
                    retval.${att.@fieldName} = IOUtils.toString(${att.@fieldName}Stream ,"UTF-8");
                }			
            <#else>
                retval.${att.@fieldName} = rs.getString(${position} + offset);
            </#if>
        <#elseif att?node_name=="long">
                long ${att.@fieldName} = rs.getLong(${position} + offset);
                if (!rs.wasNull()) {
                    retval.${att.@fieldName} = ${att.@fieldName};
                } else {
                    retval.${att.@fieldName} = null;
                }
        <#elseif att?node_name=="double">
                double ${att.@fieldName} = rs.getDouble(${position} + offset);
                if (!rs.wasNull()) {
                    retval.${att.@fieldName} = ${att.@fieldName};
                } else {
                    retval.${att.@fieldName} = null;
                }
        <#elseif att?node_name=="integer">
                int ${att.@fieldName} = rs.getInt(${position} + offset);
                if (!rs.wasNull()) {
                    retval.${att.@fieldName} = ${att.@fieldName};
                } else {
                    retval.${att.@fieldName} = null;
                }
        <#elseif att?node_name=="enum">
                retval.${att.@fieldName} = <#if !att.@scope[0]?? || att.@scope[0] == "local">${object.@name}.</#if>${att.@className}.valueOfNullSafe(rs.getString(${position} + offset));
        <#else>
                ERROR, unsupported attribute type ${att?node_name}
        </#if>
    </#if>
</#macro>

<#macro copy_dates>
    <#assign position = position + 1>
                position = ${position};
                if (rs.getTimestamp(${position} + offset) != null) {
                    retval.creationDate = new Date(rs.getTimestamp(${position} + offset).getTime());
                }
    <#assign position = position + 1>
                position = ${position};
                if (rs.getTimestamp(${position} + offset) != null) {
                    retval.updateDate = new Date(rs.getTimestamp(${position} + offset).getTime());
                }
</#macro>

<#macro copy_resultset_to_json att>
    <#if att?node_name != "object" && att?node_type == "element">
                    writer.print(',');
    <#assign position = position + 1>
                    position = ${position};
        <#if att?node_name == "date">
                    writer.date(${object.@name}Fields.${att.@fieldName}.name(), rs.getDate(${position} + offset), rs.wasNull());
        <#elseif att?node_name=="datetime">
                    writer.dateTime(${object.@name}Fields.${att.@fieldName}.name(), rs.getTimestamp(${position} + offset), rs.wasNull());
        <#elseif att?node_name=="long" || att?node_name=="integer" || att?node_name=="double">
                    writer.rs(${object.@name}Fields.${att.@fieldName}.sqlType, ${object.@name}Fields.${att.@fieldName}.name(), rs.getString(${position} + offset));
        <#elseif att?node_name=="html">
                    writer.stream(${object.@name}Fields.${att.@fieldName}.name(), rs.getBinaryStream(${position} + offset), rs.wasNull());
        <#elseif att?node_name=="boolean">
                    Boolean _${att.@fieldName} = rs.getBoolean(${position} + offset);
                    if (rs.wasNull()) _${att.@fieldName} = null;
                    writer.kvp(${object.@name}Fields.${att.@fieldName}.name(), _${att.@fieldName});
        <#elseif att?node_name=="string">
                    writer.rs(${object.@name}Fields.${att.@fieldName}.sqlType, ${object.@name}Fields.${att.@fieldName}.name(), rs.getString(${position} + offset));
        <#elseif att?node_name=="enum">
                    writer.quoted(${object.@name}Fields.${att.@fieldName}.name(), rs.getString(${position} + offset));
        <#else>
        ERROR, unsupported attribute type ${att?node_name}
        </#if>
    </#if>
</#macro>
<#macro copy_dates_to_json>
    <#assign position = position + 1>
                    position = ${position};
                    writer.print(',');
                    writer.dateTime("creationDate", rs.getTimestamp(${position} + offset));
    <#assign position = position + 1>
                    position = ${position};
                    writer.print(',');
                    writer.dateTime("updateDate", rs.getTimestamp(${position} + offset));
</#macro>

<#macro copy_pojo_to_json att>
    <#if att?node_name != "object" && att?node_type == "element">
                    writer.print(',');
                        <#if att?node_name == "date">
                    writer.date(${object.@name}Fields.${att.@fieldName}.name(), pojo.get${att.@fieldName?cap_first}());
                        <#elseif att?node_name == "datetime">
                    writer.dateTime(${object.@name}Fields.${att.@fieldName}.name(), pojo.get${att.@fieldName?cap_first}());
                        <#elseif att?node_name=="long" || att?node_name=="integer" || att?node_name=="double" || att?node_name=="boolean">
                    writer.kvp(${object.@name}Fields.${att.@fieldName}.name(), pojo.get${att.@fieldName?cap_first}());
                        <#elseif att?node_name=="string" || att?node_name=="html">
                    writer.escaped(${object.@name}Fields.${att.@fieldName}.name(), pojo.get${att.@fieldName?cap_first}());
                        <#elseif att?node_name=="enum">
                    writer.quoted(${object.@name}Fields.${att.@fieldName}.name(), pojo.get${att.@fieldName?cap_first}());
                        <#else>
                        ERROR ${att?node_name}
                        </#if>
    </#if>
</#macro>
<#macro copy_pojo_dates_to_json>
                    writer.print(',');
                    writer.dateTime("creationDate", pojo.getCreationDate());
                    writer.print(',');
                    writer.dateTime("updateDate", pojo.getUpdateDate());
</#macro>

<#assign stream = "false">
<#list doc["/object/attributes/*"] as att>
    <#if att?node_name == "html">
        <#assign stream = "true">
    <#elseif att?node_name == "string" && att.@maxlength[0]?number &gt; 65000>
        <#assign stream = "true">
    </#if>
</#list>
import org.jetbrains.annotations.*;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.sql.ResultSet;
import java.sql.SQLException;
<#if object.attributes.html[0]??>
import org.apache.commons.io.IOUtils;
</#if>
import java.text.ParseException;
import java.util.Date;
<#if doc["//enum[@scope='global']"][0]??>
import ${object.@package}.model.enums.*;
</#if>

<#if object.lazyScope[0]??>
import java.util.HashMap;
import java.util.Map;
</#if>
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import java.util.logging.Logger;

import ${object.@package}.model.*;
import ${object.@package}.model.base.${object.@name}Base;
import ${object.@package}.model.fields.*;
import redora.exceptions.*;
import redora.util.JSONWriter;

import static redora.db.SQLParameter.yyyyMMdd;
import static redora.db.SQLParameter.yyyyMMddHHMMSS;

/**
* Utility to move around data from DB to pojo or from DB to JSON.
* Use statical like:
* <code>
* ${object.@name}Util.copy(resultSet, 0, redora.api.fetch.Scope.Table, myPojo);
* </code>
* @author Redora (www.redora.net)
*/
public class ${object.@name}Util {
    private static final transient Logger l = Logger.getLogger("${object.@package}.service.${object.@name}Util");

    /**
    * Copies the ResultSet into a ${object.@name}.
    * The ResultSet is parsed by index for maximum performance. By default this starts
    * at 1, but you can add an offset if you have used a query with a field list with other
    * object(s) than ${object.@name}. For normal queries set offset = 0.
    * @param rs	(Mandatory) ResultSet containing all fields listed in ${object.@name}Fields
    * @param offset (Mandatory) Almost always 0. When performing a query on multiple tables give the offset of the fields in the select.
    * @param scope  (Mandatory) The Fetch Scope
    * @param retval (Mandatory) The ${object.@name} you wish to decorate with the information from the ResultSet.
    * @throws CopyException On error when copying from the ResultSet
    * @see ${object.@name}Fields
    */
    public static void copy(@NotNull ResultSet rs, int offset, @NotNull redora.api.fetch.Scope scope
                            , @NotNull ${object.@name}Base retval) throws CopyException {
        retval.fetchScope = scope;
        int position = 1;
        try {
            retval.id = rs.getLong(1 + offset);
            if (scope == redora.api.fetch.Scope.List) {
<#assign position = 1>
<#if object.listScope[0]??>
    <#list object.listScope?children as att>
        <@copy_resultset_to_pojo att=att />
    </#list>
</#if>
            } else if (scope == redora.api.fetch.Scope.Table) {
<#assign position = 1>
<#list object.tableScope?children as att>
    <@copy_resultset_to_pojo att=att />
</#list>
<@copy_dates />
            } else if (scope == redora.api.fetch.Scope.Form) {
<#assign position = 1>
<#list object.formScope?children as att>
    <@copy_resultset_to_pojo att=att />
</#list>
<@copy_dates />
            } else {
                throw new CopyException("Unsupported scope " + scope);
            }
<#if stream == "true">
        } catch (IOException e) {
            throw new CopyException("Failed to stream from field to object ${object.@name} at field id/scope/position/offset: " + retval.id + "/" + scope + "/" + position + "/" + offset, e);
</#if>
        } catch (SQLException e) {
            String columns = "";
            try {
                for (int i = 1; i < rs.getMetaData().getColumnCount(); i++)
                    columns += rs.getMetaData().getColumnName(i) + " [" + i + "],";
            } catch (SQLException e1) {} //ignore
            l.log(INFO, "Columns for following exception {0}", columns);
            throw new CopyException("Failed to copy from field to object ${object.@name} at field id/scope/position/offset: " + retval.id + "/" + scope + "/" + position + "/" + offset, e);
        }
    }
  	
<#if object.lazyScope[0]??>
    /**
    * Copies lazy loading field resultset into a Map. All the lazy=true fields will be fetched in one stroke,
    * however, fields that are not in ${object.@name} are unfetched: lazy object's and set's.
    * 
    * @param rs	(Mandatory) ResultSet containing all fields listed in ${object.@name}Fields
    * @param offset	(Mandatory) Almost always 0. When performing a query on multiple tables give the offset of the fields in the select.
    * @return Always filled Map. If there are no lazy fields, this method would not exist.
    * @throws CopyException On error when copying from ResultSet
    * @see ${object.@name}Fields
    */
    @NotNull
    public static Map<String, Object> copyLazy(@NotNull ResultSet rs, int offset) throws CopyException {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
    <#assign position = 1>
    <#list object.lazyScope?children as att>
        <#if att?node_name == "string" || att?node_name == "html">
            <#assign position = position + 1>
            <#if att?node_name == "html" || att.@maxlength[0]?number &gt; 65000>
            java.io.InputStream _${att.@fieldName}Stream = rs.getBinaryStream(${position} + offset);
            if (_${att.@fieldName}Stream != null) {
                map.put(${object.@name}Fields.${att.@fieldName}.name(), IOUtils.toString(_${att.@fieldName}Stream));
            }
            <#else>
            String ${att.@fieldName} = rs.getString(${position} + offset);
            map.put(${object.@name}Fields.${att.@fieldName}.name(), ${att.@fieldName});
            </#if>
        </#if>
    </#list>
        } catch (Exception e) {
            throw new CopyException("Failed to copy lazy field from table to Map",e);
        }
        return map;
    }
</#if>

    /**
    * Copies te resultset into a JSON stream.
    * The resultset is default parsed by index for maximum performance.
    * @param rs   (Mandatory) ResultSet containing all fields listed in ${object.@name}Fields
    * @param offset (Mandatory) Almost always 0. When performing a query on multiple tables give the offset of the fields in the select.
    * @param writer (Mandatory) The output stream as JSON writer, typically routed to your servlets' writer stream.
    * @throws CopyException On error when copying from ResultSet
    **/
    public static void jsonStream(@NotNull ResultSet rs, int offset, @NotNull JSONWriter writer, @NotNull redora.api.fetch.Scope scope) throws CopyException {
        int position = 1;
        Long id = null;
        try {
            writer.print('{');
            id = rs.getLong(1 + offset); //id is not null
            writer.kvp("id", id);
            writer.print(',');
            writer.kvp("_scope", scope.ordinal());
            switch (scope) {
                case List: {
<#assign position = 1>
<#if object.listScope[0]??>
    <#list object.listScope?children as att>
        <@copy_resultset_to_json att=att />
    </#list>
</#if>
                    break;
                }
                case Table: {
<#assign position = 1>
<#list object.tableScope?children as att>
    <@copy_resultset_to_json att=att />
</#list>
<@copy_dates_to_json />
                    break;
                }
                case Form: {
<#assign position = 1>
<#list object.formScope?children as att>
    <@copy_resultset_to_json att=att />
</#list>
<@copy_dates_to_json />
                    break;
                }
                case Lazy: {
<#assign position = 1>
<#if object.lazyScope[0]??>
    <#list object.lazyScope?children as att>
        <@copy_resultset_to_json att=att />
    </#list>
</#if>
                    break;
                }
                default: {
                    throw new IllegalArgumentException("Unsupported scope " + scope);
                }
            }
            writer.print('}');
        } catch (Exception e) {
            String columns = "";
            try {
                for (int i = 1; i < rs.getMetaData().getColumnCount(); i++)
                    columns += rs.getMetaData().getColumnName(i) + " [" + i + "],";
            } catch (SQLException e1) {} //ignore
            l.log(INFO, "Columns for following exception {0}", columns);
            throw new CopyException("Failed to copy from ResultSet into JSON for ${object.@name} at field id/scope/position/offset: " + id + "/" + scope + "/" + position + "/" + offset,e);
        }
    }

    /**
    * Copies ${object.@name} to JSONWriter.
    **/
    public static void copy(@NotNull ${object.@name} pojo, @NotNull JSONWriter writer)
            throws JSONException, CopyException {
        try {
            writer.print('{');
            writer.kvp("id", pojo.getId());
            writer.print(',');
            writer.kvp("_scope", pojo.fetchScope.ordinal());
            switch (pojo.fetchScope) {
                case List: {
<#assign position = 1>
<#if object.listScope[0]??>
    <#list object.listScope?children as att>
        <@copy_pojo_to_json att=att />
    </#list>
</#if>
                    break;
                }
                case Table: {
<#assign position = 1>
<#list object.tableScope?children as att>
    <@copy_pojo_to_json att=att />
</#list>
<@copy_pojo_dates_to_json />
                    break;
                }
                case Form: {
<#assign position = 1>
<#list object.formScope?children as att>
    <@copy_pojo_to_json att=att />
</#list>
<@copy_pojo_dates_to_json />
                    break;
                }
                default: {
                    throw new IllegalArgumentException("Unsupported scope " + pojo.fetchScope);
                }
            }
            writer.print('}');
        } catch (Exception e) {
            throw new CopyException("Failed to copy from pojo into JSON for ${object.@name}",e);
        }
    }

    /**
    * Decorates returned ${object.@name} with the values provided with the HTTP request.
    * If the request contains an id parameter, the corresponding ${object.@name} will be
    * fetched from the DB first. Otherwise a new ${object.@name} will be created.
    * The request object should contain parameters like "${object.@name}.[attributeName]="
    * which will be set through the ${object.@name} setters. The client (that created the request object)
    * can omit attributes that it does not have or those that didn't change. If an attribute needs
    * to be nullified, simply supply it as empty.<br>
    * @param request Request containing parameters like "${object.@name}.[attributeName]="
    * @throws CopyException When a ${object.@name} is updated, a FindById is performed to get the original. When this fails, this is the exception.
    */
    @NotNull
    public static ${object.@name} from(@NotNull HttpServletRequest request)
            throws CopyException {
        final ${object.@name} pojo;
        if (StringUtils.isNotEmpty(request.getParameter("id"))) {
            ${object.@name}Service service = null;
            try {
                service = ServiceFactory.${object.@name?uncap_first}Service();
                pojo = service.findById(Long.valueOf(request.getParameter("id")), redora.api.fetch.Scope.Form);
            } catch (RedoraException e) {
                l.log(SEVERE, "Failed to retrieve for persist ${object.@name}: " + request.getParameter("id"), e);
                throw new CopyException("Failed to retrieve for persist ${object.@name}: " + request.getParameter("id"), e);
            } finally {
                ServiceFactory.close(service);
            }
        } else {
            pojo = new ${object.@name}();
        }
            <#list object.attributes?children as att>
                <#if att?node_name == "long">
        if (request.getParameterMap().containsKey("${att.@fieldName}")) {
            if (!StringUtils.isEmpty(request.getParameter("${att.@fieldName}")))
                pojo.set${att.@fieldName?cap_first}(Long.valueOf(request.getParameter("${att.@fieldName}")));
            else
                pojo.set${att.@fieldName?cap_first}(null);
        }
                <#elseif att?node_name == "integer">
        if (request.getParameterMap().containsKey("${att.@fieldName}")) {
            if (!StringUtils.isBlank(request.getParameter("${att.@fieldName}")))
                pojo.set${att.@fieldName?cap_first}(Integer.parseInt(request.getParameter("${att.@fieldName}")));
            else 
                pojo.set${att.@fieldName?cap_first}(null);	
        }
                <#elseif att?node_name == "double">
        if (request.getParameterMap().containsKey("${att.@fieldName}")) {
            if (!StringUtils.isBlank(request.getParameter("${att.@fieldName}")))
                pojo.set${att.@fieldName?cap_first}(Double.valueOf(request.getParameter("${att.@fieldName}")));
            else 
                pojo.set${att.@fieldName?cap_first}(null);	
        }
                <#elseif att?node_name == "string">
        if (request.getParameterMap().containsKey("${att.@fieldName}")) {
                    <#if att.@lazy=="true">
            try {
                if (!StringUtils.isEmpty(request.getParameter("${att.@fieldName}")))
                    pojo.set${att.@fieldName?cap_first}(request.getParameter("${att.@fieldName}"));
                else 
                    pojo.set${att.@fieldName?cap_first}(null);
            } catch(LazyException e) {
                throw new CopyException("Failed to retrieved the lazy ${att.@fieldName}", e);
            }
                    <#else>
            if (!StringUtils.isBlank(request.getParameter("${att.@fieldName}")))
                pojo.set${att.@fieldName?cap_first}(request.getParameter("${att.@fieldName}"));
            else 
                pojo.set${att.@fieldName?cap_first}(null);
                    </#if>
        }
                <#elseif att?node_name == "html">
        if (request.getParameterMap().containsKey("${att.@fieldName}")) {
            try {
                if (!StringUtils.isEmpty(request.getParameter("${att.@fieldName}"))) 
                    pojo.set${att.@fieldName?cap_first}(request.getParameter("${att.@fieldName}"));
                else
                    pojo.set${att.@fieldName?cap_first}(null);
            } catch(RedoraException e) {
                throw new CopyException("Failed to retrieved the html field ${att.@fieldName}", e);
            }
        }
                <#elseif att?node_name == "enum">
        if (request.getParameterMap().containsKey("${att.@fieldName}")) {
            if (!StringUtils.isEmpty(request.getParameter("${att.@fieldName}"))) 
                pojo.set${att.@fieldName?cap_first}(<#if !att.@scope[0]?? || att.@scope[0] == "local">${object.@name}.</#if>${att.@className}.valueOfNullSafe(request.getParameter("${att.@fieldName}")));
            else
                pojo.set${att.@fieldName?cap_first}(null);
        }
                <#elseif att?node_name == "date" || att?node_name == "datetime">
        if (request.getParameterMap().containsKey("${att.@fieldName}")) {
            if (!StringUtils.isEmpty(request.getParameter("${att.@fieldName}"))) {
                try {
                    pojo.set${att.@fieldName?cap_first}(yyyyMMdd<#if att?node_name == "datetime">HHMMSS</#if>.parse(request.getParameter("${att.@fieldName}")));
                } catch(ParseException e) {
                    throw new CopyException("Failed to copy this date: " + request.getParameter("${att.@fieldName}"), e);
                }
            } else
                pojo.set${att.@fieldName?cap_first}(null);
        }
                <#elseif att?node_name == "set">
                    <#if att.@multiplicity="n-to-m">
        if (!StringUtils.isBlank(request.getParameter("${att.@theirName}Id"))) {
            ${att.@class}Service service = null;
            String id = null;
            try {
                service = ServiceFactory.${att.@class?uncap_first}Service();
                for (String _id : request.getParameter("${att.@theirName}Id").split(",")) {
                    id = _id; //Keep a copy for exception handling
                    pojo.get${att.@fieldName?cap_first}().add(service.findById(Long.valueOf(id), redora.api.fetch.Scope.Table));
                }
            } catch(RedoraException e) {
                throw new CopyException("Copy failed with pojo.get${att.@fieldName?cap_first}(id = " + id + ")", e);
            } finally {
                ServiceFactory.close(service);
            }
        }
                    </#if>
                <#elseif att?node_name == "boolean">
        if (request.getParameterMap().containsKey("${att.@fieldName}")) {
            if (!StringUtils.isEmpty(request.getParameter("${att.@fieldName}")))
                pojo.set${att.@fieldName?cap_first}(Boolean.valueOf(request.getParameter("${att.@fieldName}")));
            else 
                pojo.set${att.@fieldName?cap_first}(null);
        }
                </#if>
            </#list>
        if (request.getParameterMap().containsKey("creationDate")) {
            if (!StringUtils.isEmpty(request.getParameter("creationDate"))) {
                try {
                    pojo.creationDate = new Date((yyyyMMddHHMMSS.parse(request.getParameter("creationDate")).getTime()/1000)*1000);
                } catch(ParseException e) {
                    throw new CopyException("Failed to copy this date: " + request.getParameter("creationDate"), e);
                }
            }
        }
        if (request.getParameterMap().containsKey("updateDate")) {
            if (!StringUtils.isEmpty(request.getParameter("updateDate"))) {
                try {
                    pojo.updateDate = new Date((yyyyMMddHHMMSS.parse(request.getParameter("updateDate")).getTime()/1000)*1000);
                } catch(ParseException e) {
                    throw new CopyException("Failed to copy this date: " + request.getParameter("updateDate"), e);
                }
            }
        }
        return pojo;
    }
}
